/*
 *  Cero Project - Copyright   2006 The Cero Developement Team 
 *  (Michael Laguerre, Camille Roux, Matthieu Segret, Mathieu Sivade) 
 * 
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the Free 
 *  Software Foundation; either version 2 of the License, or (at your option) 
 *  any later version.
 * 
 *  This program is distributed in the hope that it will be useful, but 
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 */

package org.ceroproject;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * ConfigurationManager Class
 * 
 * This kind of objects is used in order to save properties in a XML file. It
 * can save Integer,String and List object.
 * 
 * 
 * @author Roux Camille
 */
public class ConfigurationManager {

	private Document document;

	private DocumentBuilder builder;

	/** the XML file where we save the properties */
	private String file;

	/** the list of the Configurers */
	private List<Configurer> configurerList;

	/**
	 * Create an object ConfigurationManager. This kind of objects is used in
	 * order to save properties in a XML file. It can save Integer,String and
	 * Collection object.
	 * 
	 * @param file
	 *            the name of the XML file.
	 * @throws ConfigurationException if one of the values in the file was invalid
	 * @throws ParserConfigurationException if your java implementation doesn't provide satisfying XML parsers (shouldn't happen, but who knows) 
	 * @throws IOException if the file couldn't be accessed
	 * @throws SAXException if the file couldn't be parsed properly
	 */
	public ConfigurationManager(String file) throws ConfigurationException, ParserConfigurationException, SAXException, IOException {
		// Document builder factory creation
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

		// Document builder creation
		builder = factory.newDocumentBuilder();
		this.file = file;
		File xml = new File(file);
		configurerList = new ArrayList<Configurer>();

		if (xml.exists()) {
			// XML file parsing
			document = builder.parse(xml);
			createConfigurers();
		}
	}

	/**
	 * Save the Configurers in the XML file
	 * 
	 */
	public void save() {
		document = builder.newDocument();
		document.setXmlVersion("1.0");
		document.setXmlStandalone(true);
		Element root = document.createElement("CONFIGURATION");
		for (Configurer c : configurerList) {
			Element configurer = document.createElement("CONFIGURER");
			configurer.setAttribute("id", c.getId());
			root.appendChild(configurer);
			// write the INT tags
			for (String i : c.getIntProperties().keySet()) {
				Element intElement = document.createElement("INT");
				intElement.setAttribute("name", i);
				intElement.setAttribute("value", c.getIntProperties().get(i)
						.toString());
				configurer.appendChild(intElement);
			}
			// write the STRING tags
			for (String i : c.getStringProperties().keySet()) {
				Element stringElement = document.createElement("STRING");
				stringElement.setAttribute("name", i);
				stringElement.setAttribute("value", c.getStringProperties()
						.get(i));
				configurer.appendChild(stringElement);
			}
			// write the LIST tags
			for (String i : c.getListProperties().keySet()) {
				Element listElement = document.createElement("LIST");
				listElement.setAttribute("name", i);
				for (String value : c.getListProperties().get(i)) {
					Element listItemElement = document
							.createElement("LISTITEM");
					listItemElement.setTextContent(value);
					listElement.appendChild(listItemElement);
				}
				configurer.appendChild(listElement);
			}
		}
		document.appendChild(root);
		// transform the DOM object in a XML file
		transformerXml(document, file);
	}

	/**
	 * This method recreate the Configurers from the DOM object
	 * 
	 */
	private void createConfigurers() throws ConfigurationException {
		Element root = document.getDocumentElement();
		NodeList list = root.getElementsByTagName("CONFIGURER");
		for (int i = 0; i < list.getLength(); i++) {
			Element e = (Element) list.item(i);
			Configurer configurer = new Configurer(e.getAttribute("id"));
			// int tags
			NodeList tempList = e.getElementsByTagName("INT");
			for (int j = 0; j < tempList.getLength(); j++) {
				Element element = (Element) tempList.item(j);
				configurer.setInt(element.getAttribute("name"), Integer
						.valueOf(element.getAttribute("value")));
			}
			// string tags
			tempList = e.getElementsByTagName("STRING");
			for (int j = 0; j < tempList.getLength(); j++) {
				Element element = (Element) tempList.item(j);
				configurer.setString(element.getAttribute("name"), element
						.getAttribute("value"));
			}
			// list tags
			tempList = e.getElementsByTagName("LIST");
			for (int j = 0; j < tempList.getLength(); j++) {
				Element element = (Element) tempList.item(j);
				// listitem tags
				NodeList tempSubList = element.getElementsByTagName("LISTITEM");
				List<String> stringList = new ArrayList<String>();
				for (int k = 0; k < tempSubList.getLength(); k++) {
					Element listItem = (Element) tempSubList.item(k);
					stringList.add(listItem.getTextContent());
				}
				configurer.setList(element.getAttribute("name"), stringList);
			}
			configurerList.add(configurer);
		}
	}

	/**
	 * Configure a Configurable object Set the Configurer with the good ID if it
	 * exists, set the default Configurer otherwise.
	 * 
	 * @param c
	 *            object to configure
	 * @throws ConfigurationException 
	 */
	public void configure(Configurable c) throws ConfigurationException {
		// TODO : verify if the id is correct...
		String id = c.getClass().getPackage() + " : " + c.getClass().getName();
		for (Configurer configurer : configurerList) {
			if (configurer.getId().equals(id)) {
				copy(configurer,c.getConfiguration());
				// exit the loop if the Configurer of the Configurable exists
				return;
			}
		}
		// if the the Configurer of the Configurable doesn't exist
		copy(c.getDefaultConfiguration(),c.getConfiguration());
	}

	/**
	 * Copy all the parameters of a configurer in an other one
	 * 
	 * @param from the configurer which informations are copied from
	 * @param to the configurer where informations are copied to
	 * @throws ConfigurationException a parameter can't be copied
	 */
	private static void copy(Configurer from, Configurer to) throws ConfigurationException{
		for(String key : from.getIntProperties().keySet()){
			to.setInt(key,from.getInt(key));
		}
		for(String key : from.getStringProperties().keySet()){
			to.setString(key,from.getString(key));
		}
		for(String key : from.getListProperties().keySet()){
			to.setList(key,from.getList(key));
		}
	}
	/**
	 * Transform a Document in a file
	 * 
	 * @param document
	 *            document to transform
	 * @param dest
	 */
	private static void transformerXml(Document document, String dest) {
		try {
			// DOM source creation
			Source source = new DOMSource(document);

			// File creation
			File file = new File(dest);
			Result result = new StreamResult(file);

			// Transformer configuration
			TransformerFactory fabrique = TransformerFactory.newInstance();
			Transformer transformer = fabrique.newTransformer();
			// the ouput properties
			Properties proprietes = new Properties();
			proprietes.put("method", "xml");
			proprietes.put("version", "1.0");
			proprietes.put("encoding", "utf-8");
			proprietes.put("standalone", "yes");
			proprietes.put("indent", "yes");
			proprietes.put("omit-xml-declaration", "no");
			proprietes.put("doctype-system", "./cero/games/configuration.dtd");

			transformer.setOutputProperties(proprietes);

			transformer.transform(source, result);
		} catch (Exception e) {
			// TODO:throw error
			System.out.println("File Reading Error : " + dest);
		}
	}

}
